/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */

package com.liycloud.common.security.component;

// 工具库 hutool
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;

import com.liycloud.common.core.constant.CommonConstants;
import com.liycloud.common.core.constant.SecurityConstants;
import com.liycloud.common.security.exception.PigxAuth2Exception;
import com.liycloud.common.security.service.PigxUser;
import com.liycloud.common.security.util.PigxSecurityMessageSourceUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @author lengleng
 * @date 2019-03-07
 * <p>
 * 资源服务器 认证对象解析配置:
 * <p>
 *
 * 告诉框架 用户信息user_info(基本信息和权限等)  和 认证对象Authentication 如何进行转换
 *
 * Authentication  <->   Map<String, ?>     这两者相互转换
 */
@Slf4j
public class PigxUserAuthenticationConverter implements UserAuthenticationConverter {

	private static final String N_A = "N/A";

	/**
	 * 说白了就是: 用户携带的 access_token, 认证成功后将会转换为 Authentication  (eg. UsernamePasswordAuthenticationToken)
	 * Authentication 就代表了用户的身份认证,其中包含了用户的核心信息
	 * 该方法就是将要把 Authentication 转换为 Map<String, ? > 这样的形式
	 *
	 * Extract information about the user  used in an access token (i.e. for resource servers).
	 * 提取 访问令牌中使用的用户的信息
	 *
	 * @param authentication an authentication representing a user  代表用户的 身份验证
	 * @return a map of key values representing the unique information about the user
	 */
	@Override
	public Map<String, ?> convertUserAuthentication(Authentication authentication) {
		Map<String, Object> map = new LinkedHashMap<>();

		// USERNAME
		map.put(USERNAME, authentication.getName());

		// 权限列表不是为空
		if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
			// 权限集合
			Set<String> authoritySet = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
			map.put(AUTHORITIES, authoritySet);
		}
		return map;
	}


	/**
	 * Inverse of {@link #convertUserAuthentication(Authentication)}. Extracts an Authentication from a map.
	 * @param responseMap a map of user information
	 * @return an Authentication representing the user or null if there is none
	 */
	@Override
	public Authentication extractAuthentication(Map<String, ?> responseMap) {

		if (responseMap.containsKey(USERNAME)) { // 前提是包含 USERNAME

			Collection<? extends GrantedAuthority> authorities = getAuthorities(responseMap);

			Map<String, ?> map = MapUtil.get(responseMap, SecurityConstants.DETAILS_USER, Map.class);  // user_info

			// 校验 map 合法性
			validateTenantId(map);

			// 额外的用户信息数据
			String username = MapUtil.getStr(map, SecurityConstants.DETAILS_USERNAME);
			Integer id = MapUtil.getInt(map, SecurityConstants.DETAILS_USER_ID);
			Integer deptId = MapUtil.getInt(map, SecurityConstants.DETAILS_DEPT_ID);
			Integer tenantId = MapUtil.getInt(map, SecurityConstants.DETAILS_TENANT_ID);
			String phone = MapUtil.getStr(map, SecurityConstants.DETAILS_PHONE);
			String avatar = MapUtil.getStr(map, SecurityConstants.DETAILS_AVATAR);

			// 系统用户对象
			PigxUser user = new PigxUser(id, deptId, phone, avatar, tenantId, username, N_A, true, true, true, true, authorities);


			// 注意这里还是 UsernamePasswordAuthenticationToken  身份认证对象
			return new UsernamePasswordAuthenticationToken(user, N_A, authorities);

			/*
			 *
			 * @param Object principal     adj. 主要的,主成分
	         * @param Object credentials   n. 证书；凭据
	 		 * @param Collection<? extends GrantedAuthority> authorities  权限集合

			UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

			*/

		}
		return null;
	}


	// Map<String, ?>  转换为  Collection<? extends GrantedAuthority>
	private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map) {
		Object authorities = map.get(AUTHORITIES);
		if (authorities instanceof String) {
			return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
		}
		if (authorities instanceof Collection) {
			return AuthorityUtils.commaSeparatedStringToAuthorityList(
					StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
		}
		return AuthorityUtils.NO_AUTHORITIES;
	}


	//  校验 map 合法性
	private void validateTenantId(Map<String, ?> map) {

		String headerValue = getCurrentTenantId();

		Integer userValue = MapUtil.getInt(map, SecurityConstants.DETAILS_TENANT_ID);

		if (StrUtil.isNotBlank(headerValue) && !userValue.toString().equals(headerValue)) {
			log.warn("请求头中的租户ID({})和用户的租户ID({})不一致", headerValue, userValue);

			// TODO: 不要提示租户ID不对，可能被穷举
			throw new PigxAuth2Exception(
					PigxSecurityMessageSourceUtil.getAccessor().getMessage(
					"AbstractUserDetailsAuthenticationProvider.badTenantId",
							new Object[] { headerValue },
					"Bad tenant ID")
			);
		}
	}


	/**
	 * 获取当前请求对象: HttpServletRequest
	 * @return HttpServletRequest
	 */
	private HttpServletRequest getCurrentHttpRequest() {

		// 获取当前请求 RequestAttributes
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

		// ServletRequestAttributes  isAssignableFrom  RequestAttributes  Type类型判断  是否有继承关系
		boolean assignableFrom = ServletRequestAttributes.class.isAssignableFrom(requestAttributes.getClass());

		if(assignableFrom){  // 满足类型Type继承
			// 强制转换为子类
			ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
			// 获取 HttpServletRequest
			HttpServletRequest httpServletRequest = servletRequestAttributes.getRequest();
			return  httpServletRequest;
		}
		return null;
	}

	/**
	 * 从当前请求中获取 tenantId  租户ID
	 * @return
	 */
	private String getCurrentTenantId() {
		HttpServletRequest currentHttpRequest = getCurrentHttpRequest();
		if(currentHttpRequest !=null){
			String header = currentHttpRequest.getHeader(CommonConstants.TENANT_ID);
			return header;
		}
		return null;
	}


/*

     // 上边是普通写法
	// 高级 lambda 写法, 同时使用了 Optional<T> 避免空指针异常的问题
	private Optional<HttpServletRequest> getCurrentHttpRequest() {
		return Optional
				.ofNullable(RequestContextHolder.getRequestAttributes())
				.filter(requestAttributes -> ServletRequestAttributes.class.isAssignableFrom(requestAttributes.getClass()))
				.map(requestAttributes -> ((ServletRequestAttributes) requestAttributes))
				.map(ServletRequestAttributes::getRequest);
	}

	private String getCurrentTenantId() {
		return getCurrentHttpRequest().map(httpServletRequest -> httpServletRequest.getHeader(CommonConstants.TENANT_ID)).orElse(null);
	}


*/

}
